"""
OAuth 2.0 核心服务, 遵循 RFC 6749, RFC 6750, RFC 7009。
"""
import json
import jwt
from datetime import datetime, timezone
from typing import Any, Dict, Tuple

from jwt import PyJWTError

from axiom_boot.auth.grant_handlers.base_user_authenticator import BaseUserAuthenticator
from axiom_boot.auth.models import (Principal, TokenIssueRequestDTO)
from axiom_boot.auth.services.client_verifier import BaseClientVerifier, ClientInfo
from axiom_boot.auth.services.enhancer import TokenEnhancer
from axiom_boot.auth.services.token_service import TokenService
from axiom_boot.cache.service import CacheService
from axiom_boot.core.exceptions import Unauthorized, BusinessException
from axiom_boot.di.application_context import ApplicationContext
from axiom_boot.di.decorators import conditional_on_setting, service
from axiom_boot.di import autowired
from axiom_boot.logging.setup import get_logger

logger = get_logger(__name__)


class InvalidClientError(Unauthorized):
    """当客户端凭据无效或验证失败时抛出 (RFC 6749, 4.1.3)"""
    def __init__(self, message: str = "客户端认证失败"):
        super().__init__(message=message)


class InvalidGrantError(BusinessException):
    """
    当授权凭证（如授权码、用户名密码）无效、已过期或被撤销时抛出 (RFC 6749, 5.2)。
    这通常是客户端的错误，因此继承自 BusinessException (HTTP 200, code 400)。
    """
    def __init__(self, message: str = "授权凭证无效"):
        super().__init__(message=message)


class UnsupportedGrantTypeError(BusinessException):
    """当请求的 grant_type 不被支持时抛出 (RFC 6749, 5.2)"""
    def __init__(self, message: str = "不支持的授权类型"):
        super().__init__(message=message)


@service()
@conditional_on_setting(key="oauth_enabled")
@conditional_on_setting(key="database_enabled")
class OAuthService:
    """
    提供 OAuth 2.0 核心逻辑的服务, 包括令牌的颁发、刷新和吊销。
    """
    def __init__(
        self,
        context: ApplicationContext = autowired(),
        user_authenticator: BaseUserAuthenticator = autowired(),
        token_service: TokenService = autowired(),
        cache_service: CacheService = autowired(),
        client_verifier: BaseClientVerifier = autowired(),
    ):
        self._context = context
        self._user_authenticator = user_authenticator
        self._token_service = token_service
        self._cache_service = cache_service
        self._client_verifier = client_verifier

    async def issue_token(self, request: TokenIssueRequestDTO) -> Dict[str, Any]:
        """
        核心令牌颁发方法。根据 grant_type 分发到不同的处理逻辑。
        """
        logger.info(f"收到令牌颁发请求, grant_type='{request.grant_type}', client_id='{request.client_id}'")

        # 1. 验证客户端信息
        client = await self._client_verifier.verify_by_id(client_id=request.client_id)
        
        # 2. 根据 grant_type 获取 Principal
        if request.grant_type == "password":
            principal = await self._handle_password_grant(request, client)
        elif request.grant_type == "client_credentials":
            principal = await self._handle_client_credentials_grant(request, client)
        elif request.grant_type == "authorization_code":
            principal = await self._handle_authorization_code_grant(request, client)
        elif request.grant_type == "refresh_token":
            principal = await self._handle_refresh_token_grant(request, client)
        else:
            raise UnsupportedGrantTypeError(f"不支持的授权类型: {request.grant_type}")

        # 3. 为 Principal 颁发令牌
        logger.info(f"准备为 Principal '{principal.username}' (ID: {principal.user_id}) 颁发令牌")
        return await self._issue_tokens_for_principal(principal, client, request.scope)

    async def _handle_password_grant(self, request: TokenIssueRequestDTO, client: ClientInfo) -> Principal:
        """处理 'password' 授权类型"""
        if "password" not in client.grant_types:
            raise UnsupportedGrantTypeError("此客户端不支持 password 授权类型")
        if not request.username or not request.password:
            raise InvalidGrantError("用户名和密码是必需的")

        return await self._user_authenticator.authenticate(username=request.username, password=request.password)

    async def _handle_client_credentials_grant(self, request: TokenIssueRequestDTO, client: ClientInfo) -> Principal:
        """处理 'client_credentials' 授权类型"""
        if "client_credentials" not in client.grant_types:
            raise UnsupportedGrantTypeError("此客户端不支持 client_credentials 授权类型")
        
        # 客户端凭据模式下, Principal 代表客户端本身
        return Principal(
            sub=client.client_id,
            username=client.client_name,
            permissions=client.scope.split() if client.scope else []
        )

    async def _handle_authorization_code_grant(self, request: TokenIssueRequestDTO, client: ClientInfo) -> Principal:
        """处理 'authorization_code' 授权类型"""
        if "authorization_code" not in client.grant_types:
            raise UnsupportedGrantTypeError("此客户端不支持 authorization_code 授权类型")
        if not request.code:
            raise InvalidGrantError("缺少授权码 (code)")

        code_key = f"oauth:code:{request.code}"
        cached_data = await self._cache_service.get(code_key)
        if not cached_data:
            raise InvalidGrantError("授权码无效、已过期或已被使用")
        
        try:
            code_payload = json.loads(cached_data)
        except json.JSONDecodeError:
            raise InvalidGrantError("授权码载荷格式无效")

        await self._cache_service.delete(code_key) # 授权码一次性使用, 立即删除

        if code_payload.get("client_id") != request.client_id:
            raise InvalidGrantError("客户端ID与授权码不匹配")
        if code_payload.get("redirect_uri") != request.redirect_uri:
            raise InvalidGrantError("重定向URI与授权码不匹配")

        user_id = code_payload.get("user_id")
        return await self._user_authenticator.get_principal_by_id(user_id)

    async def _handle_refresh_token_grant(self, request: TokenIssueRequestDTO, client: ClientInfo) -> Principal:
        """处理 'refresh_token' 授权类型"""
        if "refresh_token" not in client.grant_types:
            raise UnsupportedGrantTypeError("此客户端不支持 refresh_token 授权类型")
        if not request.refresh_token:
            raise InvalidGrantError("缺少 refresh_token")

        try:
            # 1. 验证 refresh token 是否在缓存中存在 (是否有效/未吊销)
            cached_user_id = await self._cache_service.get(f"refresh_token:{request.refresh_token}")
            if not cached_user_id:
                raise InvalidGrantError("Refresh token 无效或已被吊销")

            # 2. 解码 token 并验证其内容
            payload = await self._token_service.decode_token(request.refresh_token)
            user_id = payload.get("sub")
            token_client_id = payload.get("client_id")

            if not user_id or not token_client_id or user_id != cached_user_id:
                raise InvalidGrantError("Refresh token 内容无效 (sub or client_id mismatch)")
            
            if token_client_id != client.client_id:
                raise InvalidGrantError("刷新令牌与当前客户端不匹配")

            # 3. 获取最新的 Principal
            return await self._user_authenticator.get_principal_by_id(user_id)

        except PyJWTError as e:
            raise InvalidGrantError(f"刷新令牌解码失败: {e}")

    async def _issue_tokens_for_principal(self, principal: Principal, client: ClientInfo, requested_scope_str: str | None) -> Dict[str, Any]:
        """为给定的 Principal 颁发 access_token 和 refresh_token"""
        # [DEBUG] 打印输入参数
        logger.critical(f"DEBUG: principal.permissions = {principal.permissions}")
        logger.critical(f"DEBUG: client.scope = {client.scope}")
        logger.critical(f"DEBUG: requested_scope_str = {requested_scope_str}")

        user_permissions = set(principal.permissions)
        
        if client.scope:
            # 如果客户端【设置了】scope，则它扮演“限制器”的角色，最终权限是用户和客户端的交集。
            client_allowed_scopes = set(client.scope.split())
            allowed_scope = user_permissions.intersection(client_allowed_scopes)
            logger.debug(f"客户端 '{client.client_id}' 设置了 Scope 限制，权限将基于交集。")
        else:
            # 如果客户端【未设置】scope，则默认该客户端是完全受信任的，其权限等于用户的完整权限。
            allowed_scope = user_permissions
            logger.debug(f"客户端 '{client.client_id}' 未设置 Scope 限制，默认拥有用户全部权限。")

        # 如果客户端本次请求没有指定 scope，则默认授予全部可用权限
        if requested_scope_str:
            requested_scopes = set(requested_scope_str.split())
            # 最终 scope 是三者的交集：用户拥有的、客户端被允许的、本次请求申请的
            final_scopes = allowed_scope.intersection(requested_scopes)
        else:
            # 如果不请求特定的 scope (如 refresh token 流程), 则授予所有允许的 scope
            final_scopes = allowed_scope
        
        logger.info(f"权限计算 - 用户拥有: {user_permissions}, 最终授予: {final_scopes}")

        claims = {
            "sub": principal.user_id,
            "username": principal.username,
            "roles": principal.roles,
            "permissions": list(final_scopes), # 令牌中携带的权限列表必须是最终计算出的范围
            "client_id": client.client_id,
            "scope": list(final_scopes), # 将最终计算出的 scope 存入令牌
        }

        token_enhancer = await self._context.get_optional(TokenEnhancer)
        if token_enhancer:
            claims = await token_enhancer.enhance(claims=claims, principal=principal)

        access_token = self._token_service.create_access_token(claims=claims)
        refresh_token = self._token_service.create_refresh_token(claims={"sub": principal.user_id, "client_id": client.client_id})

        # 只需缓存 refresh_token 用于吊销，access_token 通过 JTI 黑名单机制处理
        await self._cache_service.set(
            key=f"refresh_token:{refresh_token}", 
            value=principal.user_id, 
            timeout=self._token_service._refresh_expire_days * 24 * 60 * 60
        )

        return {
            "access_token": access_token,
            "refresh_token": refresh_token,
            "token_type": "bearer",
            "expires_in": self._token_service._expire_minutes * 60,
        }

    async def revoke_token(self, token_str: str, token_type_hint: str | None = None) -> None:
        """
        吊销一个令牌 (RFC 7009)。
        - 如果是 refresh_token, 从缓存中删除。
        - 如果是 access_token, 将其 JTI 加入黑名单直到过期。
        """
        # 优先处理 refresh_token, 因为它影响更广
        if token_type_hint == "refresh_token" or token_type_hint is None:
            refresh_key = f"refresh_token:{token_str}"
            if await self._cache_service.exists(refresh_key):
                await self._cache_service.delete(refresh_key)
                logger.info("刷新令牌已被吊销。")
                return # 成功吊销，直接返回

        if token_type_hint == "access_token" or token_type_hint is None:
            try:
                # 尝试将令牌作为 access_token 解码
                # 注意：这里我们不希望 decode_token 抛出异常，因此直接调用 PyJWT，不经过黑名单检查
                payload = jwt.decode(token_str, self._token_service._secret_key, algorithms=[self._token_service._algorithm])
                
                jti = payload.get("jti")
                exp = payload.get("exp")
                
                if not jti or not exp:
                    logger.warning("尝试吊销一个没有 JTI 或过期时间的令牌。")
                    return

                # 计算令牌剩余的有效期
                time_to_expire = datetime.fromtimestamp(exp, tz=timezone.utc) - datetime.now(timezone.utc)
                
                if time_to_expire.total_seconds() > 0:
                    # 将 JTI 加入黑名单，有效期为令牌剩余的生命周期
                    await self._cache_service.set(
                        key=f"jti_blacklist:{jti}",
                        value="revoked",
                        timeout=int(time_to_expire.total_seconds())
                    )
                    logger.info(f"访问令牌 (JTI: {jti}) 已被加入黑名单。")
                else:
                    logger.info("访问令牌已过期，无需加入黑名单。")
                return
            
            except PyJWTError:
                # 如果两种类型都不是，或者令牌格式错误，则静默失败
                pass
        
        logger.warning("尝试吊销一个未找到或无效的令牌。")
